package org.project.d.network.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.net.*;
import java.util.*;
import java.util.regex.Pattern;

import static java.util.Collections.emptyList;

/**
 * @author lin
 * @version V1.0
 * @description
 * @date 2020/12/3 12:34 下午
 */
@Slf4j
public class NetUtils {

    private static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$");
    private static volatile InetAddress LOCAL_ADDRESS = null;

    //    public static String getLocalAddress() {
//        try {
//            // Traversal Network interface to get the first non-loopback and non-private address
//            Enumeration<NetworkInterface> enumeration = NetworkInterface.getNetworkInterfaces();
//            ArrayList<String> ipv4Result = new ArrayList<>();
//            ArrayList<String> ipv6Result = new ArrayList<>();
//            while (enumeration.hasMoreElements()) {
//                final NetworkInterface networkInterface = enumeration.nextElement();
//                final Enumeration<InetAddress> en = networkInterface.getInetAddresses();
//                while (en.hasMoreElements()) {
//                    final InetAddress address = en.nextElement();
//                    if (!address.isLoopbackAddress()) {
//                        if (address instanceof Inet6Address) {
//                            ipv6Result.add(normalizeHostAddress(address));
//                        } else {
//                            ipv4Result.add(normalizeHostAddress(address));
//                        }
//                    }
//                }
//            }
//
//            // prefer ipv4
//            if (!ipv4Result.isEmpty()) {
//                for (String ip : ipv4Result) {
//                    if (ip.startsWith("127.0") || ip.startsWith("192.168")) {
//                        continue;
//                    }
//
//                    return ip;
//                }
//
//                return ipv4Result.get(ipv4Result.size() - 1);
//            } else if (!ipv6Result.isEmpty()) {
//                return ipv6Result.get(0);
//            }
//            //If failed to find,fall back to localhost
//            final InetAddress localHost = InetAddress.getLocalHost();
//            return normalizeHostAddress(localHost);
//        } catch (Exception e) {
//            log.error("Failed to obtain local address", e);
//        }
//
//        return null;
//    }
//
//    public static String normalizeHostAddress(final InetAddress localHost) {
//        if (localHost instanceof Inet6Address) {
//            return "[" + localHost.getHostAddress() + "]";
//        } else {
//            return localHost.getHostAddress();
//        }
//    }

    public static String getLocalAddressString() {
        if (LOCAL_ADDRESS != null) {
            return LOCAL_ADDRESS.getHostAddress();
        }
        InetAddress localAddress = getLocalAddress0();
        LOCAL_ADDRESS = localAddress;
        return localAddress.getHostAddress();
    }
    public static InetAddress getLocalAddress() {
        if (LOCAL_ADDRESS != null) {
            return LOCAL_ADDRESS;
        }
        InetAddress localAddress = getLocalAddress0();
        LOCAL_ADDRESS = localAddress;
        return localAddress;
    }

    private static InetAddress getLocalAddress0() {
        InetAddress localAddress = null;

        // @since 2.7.6, choose the {@link NetworkInterface} first
        try {
            NetworkInterface networkInterface = findNetworkInterface();
            Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
            while (addresses.hasMoreElements()) {
                Optional<InetAddress> addressOp = toValidAddress(addresses.nextElement());
                if (addressOp.isPresent()) {
                    try {
                        if (addressOp.get().isReachable(100)) {
                            return addressOp.get();
                        }
                    } catch (IOException e) {
                        // ignore
                    }
                }
            }
        } catch (Throwable e) {
            log.warn("err:{}", e);
        }

        try {
            localAddress = InetAddress.getLocalHost();
            Optional<InetAddress> addressOp = toValidAddress(localAddress);
            if (addressOp.isPresent()) {
                return addressOp.get();
            }
        } catch (Throwable e) {
            log.error("err:{}", e);
        }


        return localAddress;
    }

    private static Optional<InetAddress> toValidAddress(InetAddress address) {
        if (address instanceof Inet6Address) {
            Inet6Address v6Address = (Inet6Address) address;
            if (isPreferIPV6Address()) {
                return Optional.ofNullable(normalizeV6Address(v6Address));
            }
        }
        if (isValidV4Address(address)) {
            return Optional.of(address);
        }
        return Optional.empty();
    }

    static boolean isValidV4Address(InetAddress address) {
        if (address == null || address.isLoopbackAddress()) {
            return false;
        }

        String name = address.getHostAddress();
        return (name != null
                && IP_PATTERN.matcher(name).matches()
                && !"localhost".equals(name)
                && !"127.0.0.1".equals(name));
    }

    static boolean isPreferIPV6Address() {
        return Boolean.getBoolean("java.net.preferIPv6Addresses");
    }

    static InetAddress normalizeV6Address(Inet6Address address) {
        String addr = address.getHostAddress();
        int i = addr.lastIndexOf('%');
        if (i > 0) {
            try {
                return InetAddress.getByName(addr.substring(0, i) + '%' + address.getScopeId());
            } catch (UnknownHostException e) {
                // ignore
                log.debug("Unknown IPV6 address: ", e);
            }
        }
        return address;
    }

    public static NetworkInterface findNetworkInterface() {

        List<NetworkInterface> validNetworkInterfaces = emptyList();
        try {
            validNetworkInterfaces = getValidNetworkInterfaces();
        } catch (Throwable e) {
            log.warn("err", e);
        }

        NetworkInterface result = null;

        // Try to find the preferred one
        for (NetworkInterface networkInterface : validNetworkInterfaces) {
            if (isPreferredNetworkInterface(networkInterface)) {
                result = networkInterface;
                break;
            }
        }

        if (result == null) { // If not found, try to get the first one
            result = first(validNetworkInterfaces);
        }

        return result;
    }

    public static <T> T first(Collection<T> values) {
        if (isEmpty(values)) {
            return null;
        }
        if (values instanceof List) {
            List<T> list = (List<T>) values;
            return list.get(0);
        } else {
            return values.iterator().next();
        }
    }

    public static boolean isEmpty(Collection<?> collection) {
        return collection == null || collection.isEmpty();
    }

    private static List<NetworkInterface> getValidNetworkInterfaces() throws SocketException {
        List<NetworkInterface> validNetworkInterfaces = new LinkedList<>();
        Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
        while (interfaces.hasMoreElements()) {
            NetworkInterface networkInterface = interfaces.nextElement();
            validNetworkInterfaces.add(networkInterface);
        }
        return validNetworkInterfaces;
    }

    public static boolean isPreferredNetworkInterface(NetworkInterface networkInterface) {
        String preferredNetworkInterface = System.getProperty("project.d.network.network-car.name", "en0");
        return Objects.equals(networkInterface.getDisplayName(), preferredNetworkInterface);
    }


}
